#include "preHeader.h"
#include "TeamMgr.h"
#include "Game/CharacterMgr.h"
#include "GameServer.h"

TeamMgr::TeamMgr()
: m_teamSeed(0)
{
}

TeamMgr::~TeamMgr()
{
	for (auto& pair : m_allTeamInfos) {
		delete pair.second.pTeam;
	}
}

WheelTimerMgr *TeamMgr::GetWheelTimerMgr()
{
	return &sGameServer.sWheelTimerMgr;
}

GErrorCode TeamMgr::HandleTeamCreate(Character* pChar)
{
	if (pChar->pTeam != NULL) {
		return ErrAlreadyInTeam;
	}

	auto pTeam = new Team(++m_teamSeed);
	m_allTeamInfos[pTeam->getTeamId()] = { pTeam };
	AddTeamMember(pTeam, pChar);

	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamInvite(Character* pChar, ObjGUID targetGuid)
{
	auto pTarget = sCharacterMgr.GetCharacter(targetGuid);
	if (pTarget == NULL) {
		return TargetNotExist;
	}
	if (pTarget->pTeam != NULL) {
		return ErrTargetAlreadyInTeam;
	}

	if (pChar->pTeam == NULL) {
		return ErrNotInTeam;
	}
	if (pChar->pTeam->IsTeamFull()) {
		return ErrTeamIsFull;
	}
	if (pChar->pTeam->getTeamLeader() != pChar) {
		return ErrNotTeamLeader;
	}
	auto itr = m_allTeamInfos.find(pChar->pTeam->getTeamId());
	if (itr == m_allTeamInfos.end()) {
		return CommonInternalError;
	}
	auto& teamInfo = itr->second;
	if (teamInfo.inviteList.count(targetGuid) != 0) {
		return ErrAlreadyInTeamInviteList;
	}

	InviteInfo inviteInfo{ pChar, pTarget, NewUniqueRoutineType() };
	CreateTimer(std::bind(&TeamMgr::OnInviteRelationExpire, this,
		pChar->pTeam->getTeamId(), pTarget), inviteInfo.timer, TEAM_INVITE_TIMEOUT, 1);
	teamInfo.inviteList.emplace(targetGuid, inviteInfo);

	NetPacket pack(SMSG_TEAM_INVITE);
	pack << pChar->pTeam->getTeamId() << pChar->guid << pChar->name;
	pTarget->SendPacket(pack);

	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamInviteResp(Character* pChar, uint32 teamId, bool isAgree)
{
	if (pChar->pTeam != NULL && isAgree) {
		return ErrAlreadyInTeam;
	}

	auto itr = m_allTeamInfos.find(teamId);
	if (itr == m_allTeamInfos.end()) {
		return ErrTeamIsDisband;
	}
	auto& teamInfo = itr->second;
	auto inviteItr = teamInfo.inviteList.find(pChar->guid);
	if (inviteItr == teamInfo.inviteList.end()) {
		return ErrTeamInviteExpired;
	}

	auto inviteInfo = std::move(inviteItr->second);
	teamInfo.inviteList.erase(inviteItr);
	RemoveTimers(inviteInfo.timer);
	if (isAgree) {
		if (teamInfo.pTeam->IsTeamFull()) {
			return ErrTeamIsFull;
		}
		AddTeamMember(teamInfo.pTeam, pChar);
	}

	if (teamInfo.pTeam->IsTeamMember(inviteInfo.pInviter->guid)) {
		NetPacket pack(SMSG_TEAM_INVITE_RESP);
		pack << pChar->guid << pChar->name << isAgree;
		inviteInfo.pInviter->SendPacket(pack);
	}

	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamApply(Character* pChar, uint32 teamId)
{
	if (pChar->pTeam != NULL) {
		return ErrAlreadyInTeam;
	}

	auto itr = m_allTeamInfos.find(teamId);
	if (itr == m_allTeamInfos.end()) {
		return ErrTargetTeamNotExist;
	}
	auto& teamInfo = itr->second;
	if (teamInfo.pTeam->IsTeamFull()) {
		return ErrTeamIsFull;
	}
	if (teamInfo.applyList.count(pChar->guid) != 0) {
		return ErrAlreadyInTeamApplyList;
	}

	ApplyInfo applyInfo{ pChar, NewUniqueRoutineType() };
	CreateTimer(std::bind(&TeamMgr::OnApplyRelationExpire, this,
		teamInfo.pTeam->getTeamId(), pChar), applyInfo.timer, TEAM_APPLY_TIMEOUT, 1);
	teamInfo.applyList.emplace(pChar->guid, applyInfo);

	NetPacket pack(SMSG_TEAM_APPLY);
	pack << pChar->guid << pChar->name;
	teamInfo.pTeam->getTeamLeader()->SendPacket(pack);

	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamApplyResp(Character* pChar, ObjGUID applicantGuid, bool isAgree)
{
	auto pApplicant = sCharacterMgr.GetCharacter(applicantGuid);
	if (pApplicant == NULL) {
		return TargetNotExist;
	}
	if (pApplicant->pTeam != NULL && isAgree) {
		return ErrTargetAlreadyInTeam;
	}

	if (pChar->pTeam == NULL) {
		return ErrNotInTeam;
	}
	if (pChar->pTeam->getTeamLeader() != pChar) {
		return ErrNotTeamLeader;
	}
	auto itr = m_allTeamInfos.find(pChar->pTeam->getTeamId());
	if (itr == m_allTeamInfos.end()) {
		return CommonInternalError;
	}
	auto& teamInfo = itr->second;
	auto applyItr = teamInfo.applyList.find(applicantGuid);
	if (applyItr == teamInfo.applyList.end()) {
		return ErrTeamApplyExpired;
	}

	auto applyInfo = std::move(applyItr->second);
	teamInfo.applyList.erase(applyItr);
	RemoveTimers(applyInfo.timer);
	if (isAgree) {
		if (teamInfo.pTeam->IsTeamFull()) {
			return ErrTeamIsFull;
		}
		AddTeamMember(teamInfo.pTeam, applyInfo.pApplicant);
	}

	NetPacket pack(SMSG_TEAM_APPLY_RESP);
	pack << pChar->guid << pChar->name << isAgree;
	applyInfo.pApplicant->SendPacket(pack);

	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamLeave(Character* pChar)
{
	if (pChar->pTeam == NULL) {
		return ErrNotInTeam;
	}
	RemoveTeamMember(pChar, NULL);
	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamKick(Character* pChar, ObjGUID targetGuid)
{
	if (pChar->pTeam == NULL) {
		return ErrNotInTeam;
	}
	if (pChar->pTeam->getTeamLeader() != pChar) {
		return ErrNotTeamLeader;
	}
	auto pTarget = sCharacterMgr.GetCharacter(targetGuid);
	if (pTarget == NULL) {
		return TargetNotExist;
	}
	if (!pChar->pTeam->IsTeamMember(targetGuid)) {
		return TargetNotExist;
	}
	RemoveTeamMember(pTarget, pChar);
	return CommonSuccess;
}

GErrorCode TeamMgr::HandleTeamTransfer(Character* pChar, ObjGUID targetGuid)
{
	if (pChar->pTeam == NULL) {
		return ErrNotInTeam;
	}
	if (pChar->pTeam->getTeamLeader() != pChar) {
		return ErrNotTeamLeader;
	}
	auto pTarget = sCharacterMgr.GetCharacter(targetGuid);
	if (pTarget == NULL) {
		return TargetNotExist;
	}
	if (!pChar->pTeam->IsTeamMember(targetGuid)) {
		return TargetNotExist;
	}
	pChar->pTeam->ChangeLeader(pTarget);
	return CommonSuccess;
}

void TeamMgr::OnCharacterOnline(Character* pChar)
{
	auto pTeam = pChar->pTeam;
	if (pTeam != NULL) {
		NetPacket pack(SMSG_TEAM_MEMBER_ONLINE);
		pack << pChar->guid << pChar->name;
		pTeam->SendPacketToAllMembers(pack);
	}
}

void TeamMgr::OnCharacterOffline(Character* pChar)
{
	auto pTeam = pChar->pTeam;
	if (pTeam != NULL) {
		if (pTeam->HasTeamMemberOnline()) {
			NetPacket pack(SMSG_TEAM_MEMBER_OFFLINE);
			pack << pChar->guid << pChar->name;
			pTeam->SendPacketToAllMembers(pack);
		} else {
			DisbandTeam(pTeam->getTeamId());
		}
	}
}

Team* TeamMgr::GetTeam(uint32 teamId) const
{
	auto itr = m_allTeamInfos.find(teamId);
	return itr != m_allTeamInfos.end() ? itr->second.pTeam : NULL;
}

void TeamMgr::RemoveInviteApplyRelation(Character* pChar)
{
	for (auto& pair : m_allTeamInfos) {
		auto& teamInfo = pair.second;
		auto inviteItr = teamInfo.inviteList.find(pChar->guid);
		if (inviteItr != teamInfo.inviteList.end()) {
			RemoveTimers(inviteItr->second.timer);
			teamInfo.inviteList.erase(inviteItr);
		}
		auto applyItr = teamInfo.applyList.find(pChar->guid);
		if (applyItr != teamInfo.applyList.end()) {
			RemoveTimers(applyItr->second.timer);
			teamInfo.applyList.erase(applyItr);
		}
	}
}

void TeamMgr::OnInviteRelationExpire(uint32 teamId, Character* pTarget)
{
	auto itr = m_allTeamInfos.find(teamId);
	if (itr != m_allTeamInfos.end()) {
		auto& teamInfo = itr->second;
		auto inviteItr = teamInfo.inviteList.find(pTarget->guid);
		if (inviteItr != teamInfo.inviteList.end()) {
			teamInfo.inviteList.erase(inviteItr);
		}
	}
}

void TeamMgr::OnApplyRelationExpire(uint32 teamId, Character* pApplicant)
{
	auto itr = m_allTeamInfos.find(teamId);
	if (itr != m_allTeamInfos.end()) {
		auto& teamInfo = itr->second;
		auto applyItr = teamInfo.applyList.find(pApplicant->guid);
		if (applyItr != teamInfo.applyList.end()) {
			teamInfo.applyList.erase(applyItr);
		}
	}
}

void TeamMgr::AddTeamMember(Team* pTeam, Character* pChar)
{
	pTeam->AddMember(pChar);
	RemoveInviteApplyRelation(pChar);
}

void TeamMgr::RemoveTeamMember(Character* pChar, Character* pKicker)
{
	auto pTeam = pChar->pTeam;
	pTeam->RemoveMemeber(pChar, pKicker);
	if (!pTeam->HasTeamMemberOnline()) {
		DisbandTeam(pTeam->getTeamId());
	}
}

void TeamMgr::DisbandTeam(uint32 teamId)
{
	auto itr = m_allTeamInfos.find(teamId);
	if (itr != m_allTeamInfos.end()) {
		auto& teamInfo = itr->second;
		for (auto& pair : teamInfo.inviteList) {
			RemoveTimers(pair.second.timer);
		}
		for (auto& pair : teamInfo.applyList) {
			RemoveTimers(pair.second.timer);
		}
		teamInfo.pTeam->SendPacketToAllMembers(
			NetPacket{ SMSG_TEAM_DISBAND });
		teamInfo.pTeam->SendPacketToAllMemberInstances(
			NetPacket{ G2M_TEAM_DISBAND, teamId });
		delete teamInfo.pTeam;
		m_allTeamInfos.erase(itr);
	}
}
